package main

import (
	"context"
	"errors"
	"fmt"
	"io/fs"
	"os"
	"os/exec"
	"path/filepath"
	"runtime/debug"
	"strings"
	"time"

	"gitee.com/u-language/u-language/ucom/ast"
	"gitee.com/u-language/u-language/ucom/ast2"
	"gitee.com/u-language/u-language/ucom/cast"
	"gitee.com/u-language/u-language/ucom/cast2"
	"gitee.com/u-language/u-language/ucom/config"
	"gitee.com/u-language/u-language/ucom/errcode"
	"gitee.com/u-language/u-language/ucom/format"
	idebug "gitee.com/u-language/u-language/ucom/internal/debug"
	"gitee.com/u-language/u-language/ucom/internal/errutil"
	"gitee.com/u-language/u-language/ucom/internal/utils"
	"gitee.com/u-language/u-language/ucom/ir2"
	"gitee.com/u-language/u-language/ucom/parser"

	"github.com/spf13/cobra"
)

var (
	build     string //编译地址
	mode      string //模式
	out       string //输出文件名
	Thread    bool   = true
	cok       int
	err       error
	buildmode int // if ==1 lex -> ast , if==2 lex2 -> ast2
	buildCmd  = &cobra.Command{
		Use:   "build",
		Short: "编译源文件地址",
		Long:  "编译源文件地址",
		Run: func(cmd *cobra.Command, args []string) {
			if len(args) != 1 {
				cmd.PrintErr("build 支持处理的不是一个源文件就是一个目录下的所有.u源文件")
				os.Exit(2)
			}
			build = args[0]
			defer Recover()
			Build(build, out, buildmode)
		},
	}
	donectx    context.Context
	cancelfunc context.CancelFunc
	rootcmd    = &cobra.Command{
		Use:   "",
		Short: "U语言编译器",
	}
	build2Cmd = &cobra.Command{
		Use:   "build2",
		Short: "编译源文件地址",
		Long:  "编译源文件地址",
		Run: func(cmd *cobra.Command, args []string) {
			if len(args) != 1 {
				cmd.PrintErr("build 支持处理的不是一个源文件就是一个目录下的所有.u源文件")
				os.Exit(2)
			}
			build = args[0]
			defer Recover()
			Build2(build, out)
		},
	}
)

func init() {
	donectx, cancelfunc = context.WithCancel(context.Background())
	out, err = filepath.Abs(out)
	utils.MustErr(err)
	rootcmd.AddCommand(buildCmd)
	rootcmd.AddCommand(versionCmd)
	rootcmd.AddCommand(fmtCmd)
	rootcmd.AddCommand(build2Cmd)
	buildFlag(buildCmd)
	buildFlag(build2Cmd)
	//报错
	go errcode.Handle(donectx, func(err errcode.ErrInfo) { fmt.Println(err.String()) }, func() { os.Exit(2) })
	buildCmd.Flags().IntVar(&buildmode, "buildmode", 1, "设置编译模式")
}

func buildFlag(c *cobra.Command) {
	c.Flags().BoolVarP(&Thread, "p", "p", true, "是否使用多核")
	c.Flags().StringVarP(&mode, "mode", "m", "c", `目标文件类型
        c : c语言文件
        fasm : fasm汇编文件（实验性的，目前暂停开发）`)
	c.Flags().StringVar(&out, "o", "a"+utils.Ext, "输出文件名")
	c.Flags().CountVarP(&cok, "c", "c", "是否只输出C文件")
}

func main() {
	defer Recover()
	if idebug.Debug { //调试时操作
		fmt.Println("Thread:", Thread)
		idebug.TraceStart()
		idebug.PprofStart()
	}
	err = rootcmd.Execute()
	if err != nil {
		rootcmd.Help()
		os.Exit(2)
		return
	}
}

// SaveCFile 将C抽象语法树表示的C源代码保存到文件中，并调用C工具链编译
func SaveCFile(toc *cast.UtoC, file string, outfile string) bool {
	tmp, err := os.MkdirTemp("", "c*")
	utils.MustErr(err)
	file = filepath.Join(tmp, file)
	fd, err := os.Create(file)
	utils.MustErr(err)
	_, err = fd.WriteString(toc.C())
	utils.MustErr(err)
	fd.Close()
	clist := append([]string(nil), file)
	if toc.InAutoFree {
		clist = append(clist, utils.StdOne["mempool"].CFile...)
	}
	if _CCBuild(tmp, clist, outfile) { //调用C工具链生成可执行文件
		utils.MustErr(os.RemoveAll(tmp))
		return true
	}
	fmt.Println("C文件在", tmp)
	return false
}

// _CCBuild 调用C工具链将file编译为
func _CCBuild(tmp string, clist []string, outfile string) bool {
	if cok != 0 {
		fmt.Println("C文件在", tmp)
		os.Exit(0)
	}
	CCall := strings.Split(config.CC, "||")
	for _, cc := range CCall {
		arg := append([]string(nil), clist...)
		arg = append(arg, "-lpthread", "-o", filepath.Join(tmp, "a"+utils.Ext), "-I")
		arg = append(arg, utils.StdOne["mempool"].HDir...)
		if Exec(cc, arg) {
			utils.MustErr(utils.MoveFile(filepath.Join(tmp, "a"+utils.Ext), outfile))
			return true
		}
	}
	fmt.Println("尝试从所有c工具链生成二进制文件都失败了,没有删除c源文件")
	return false
}

var outputPrint = false

func Exec(path string, args []string) bool {
	cmd := exec.Command(path)
	cmd.Args = append(cmd.Args, args...)
	str, err := cmd.CombinedOutput()
	if outputPrint && err != nil {
		fmt.Println(string(str))
	}
	if err == nil {
		return true
	} else {
		var exiterr *exec.ExitError
		if errors.As(err, &exiterr) {
			fmt.Printf("调用C工具链 %s 生成二进制失败，退出值=%d\n", path, exiterr.ExitCode())
		} else if errors.Is(err, exec.ErrNotFound) {
			fmt.Printf("C工具链 %s 没找到\n", path)
		} else if errors.Is(err, fs.ErrNotExist) {
			fmt.Printf("C工具链 %s 没找到\n", path)
		}
	}
	return false
}

func FindC(dir string) []string {
	var ret = make([]string, 0)
	err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() && path != dir {
			return filepath.SkipDir
		}
		if filepath.Ext(info.Name()) == ".c" {
			ret = append(ret, path)
		}
		return nil
	})
	utils.MustErr(err)
	return ret
}

func Recover() {
	if idebug.Debug { //调试时操作
		idebug.PprofClose()
		idebug.TraceClose()
	}
	if err := recover(); err != nil {
		if errv, ok := err.(error); ok {
			switch {
			case errors.Is(errv, os.ErrNotExist): //如果文件不存在
				fmt.Println(build, "不存在")
				os.Exit(2)
			case errutil.IsCompileErr(errv): //如果是编译器错误
				debug.PrintStack()
				fallthrough
			case errors.Is(errv, parser.NoUFileErr), errors.Is(errv, parser.ImportLimit), errors.Is(errv, parser.FileNoExt), errors.Is(errv, parser.PathNotFound): //如果是预定义错误
				fmt.Println(errv.Error())
				os.Exit(2)
			case errors.Is(errv, format.ErrorAfterLex):
				cancelfunc()
				return
			}
		}
		if idebug.Debug {
			fmt.Println(err)
			debug.PrintStack()
		} else {
			fmt.Println("发生不能识别的错误")
		}
		os.Exit(2)
	}
}

// SaveCFileBuildMode2 将C语言转换器表示的C源代码保存到文件中，并调用C工具链编译
func SaveCFileBuildMode2(toc *cast2.UtoC, file string, outfile string) bool {
	tmp, err := os.MkdirTemp("", "c*")
	utils.MustErr(err)
	file = filepath.Join(tmp, file)
	fd, err := os.Create(file)
	utils.MustErr(err)
	_, err = fd.WriteString(toc.C(true))
	utils.MustErr(err)
	fd.Close()
	clist := append([]string(nil), file)
	// if toc.InAutoFree {
	// 	clist = append(clist, utils.StdOne["mempool"].CFile...)
	// }
	if _CCBuild(tmp, clist, outfile) { //调用C工具链生成可执行文件
		utils.MustErr(os.RemoveAll(tmp))
		return true
	}
	fmt.Println("C文件在", tmp)
	return false
}

// Build 将U代码构建
//   - path是U代码路径
//   - out是输出路径
//   - buildmode是编译模式
func Build(path string, out string, buildmode int) {
	if buildmode == 2 {
		tree, err := parser.ParserAutoBuildMode2(path, Thread, errcode.DefaultErrCtx)
		utils.MustErr(err)
		if errcode.Errbol() { //代码有错误
			cancelfunc()
			time.Sleep(1000 * time.Second) //代码有错误不应继续执行
		}
		if tree == nil { //如果是空文件
			return
		}
		switch mode {
		case "c": //c模式
			switch tree := tree.(type) {
			case *ast2.Tree:
				toc := &cast2.UtoC{}
				toc.Parser(tree) //转换为C抽象语法树
				cancelfunc()
				if idebug.Debug && config.Debug_PrintCast == "on" { //调试时操作
					fmt.Println(toc.C(true))
				} else {
					SaveCFileBuildMode2(toc, "out.c", out)
				}
			case *ast2.Package:
				p := cast2.NewPackage(Thread, tree, true)
				if idebug.Debug && config.Debug_PrintCast == "on" { //调试时操作
					fmt.Printf("%+v\n", p)
				} else {
					tmp, err := os.MkdirTemp("", "c*")
					utils.MustErr(err)
					paths, err := p.OupputC(tmp)
					clist := []string{paths}
					utils.MustErr(err)
					// if p.WithAutoFree() {
					// 	paths = append(paths, utils.StdOne["mempool"].CFile...)
					// }
					if _CCBuild(tmp, clist, out) {
						utils.MustErr(os.RemoveAll(tmp))
					} else {
						fmt.Println("C文件在", tmp)
					}
				}
			}
		case "fasm": //fasm模式
			panic(errutil.NewErr2(errutil.CompileErr, "build --buildmode=2 不支持fasm"))
		}
		return
	}
	//解析获得抽象语法树，并进行语义检查
	iface, err := parser.ParserAuto(path, Thread, errcode.DefaultErrCtx)
	utils.MustErr(err)
	if errcode.Errbol() { //代码有错误
		cancelfunc()
		time.Sleep(1000 * time.Second) //代码有错误不应继续执行
	}
	if utils.IsNil(iface) { //如果是空文件
		return
	}
	cancelfunc()
	switch mode {
	case "c": //c模式
		switch iface.(type) {
		case *ast.Tree:
			toc := cast.NewUtoC()
			ast := iface.(*ast.Tree)
			toc.Parser(ast, false)                              //转换为C抽象语法树
			if idebug.Debug && config.Debug_PrintCast == "on" { //调试时操作
				fmt.Printf("%+v\n", toc.C())
			} else {
				SaveCFile(toc, "out.c", out)
			}
		case *ast.Package:
			p := cast.NewPackage(Thread)
			astp := iface.(*ast.Package)
			p.AddUastSlice(astp, nil)
			if idebug.Debug && config.Debug_PrintCast == "on" { //调试时操作
				fmt.Printf("%+v\n", p)
			} else {
				tmp, err := os.MkdirTemp("", "c*")
				utils.MustErr(err)
				paths, err := p.OupputC(tmp)
				utils.MustErr(err)
				if p.WithAutoFree() {
					paths = append(paths, utils.StdOne["mempool"].CFile...)
				}
				if _CCBuild(tmp, paths, out) {
					utils.MustErr(os.RemoveAll(tmp))
				} else {
					fmt.Println("C文件在", tmp)
				}
			}
		}
	case "fasm": //fasm模式
		CallAmd64(iface.(*ast.Tree))
	}
}

// Build2 将U代码构建,使用ir2
//   - path是U代码路径
//   - out是输出路径
func Build2(path string, out string) {
	//解析获得ir，并进行语义检查
	result, err := parser.ParserAuto2(path, Thread, errcode.DefaultErrCtx)
	if err != nil {
		if err.Error() != "11" { //如果是未知的错误
			cancelfunc()
			panic(err)
		} else { //如果是代码解析完抽象语法树发现有错误
			cancelfunc()
			return
		}
	}
	cancelfunc()
	switch mode {
	case "c":
		switch result.(type) {
		case *ir2.File:
			f := result.(*ir2.File)
			toc := cast.NewIr2ToC(f.PackageName, f.HaveInitFunc)
			cast.PaeserIr2(toc, f)                              //转换为C抽象语法树
			if idebug.Debug && config.Debug_PrintCast == "on" { //调试时操作
				fmt.Printf("%+v\n", toc.C())
			} else {
				SaveCFile2(toc, "out.c", out)
			}
		}
	case "fasm": //fasm模式
		panic(errutil.NewErr2(errutil.CompileErr, "build2不支持fasm"))
	}
}

// SaveCFile 将C抽象语法树表示的C源代码保存到文件中，并调用C工具链编译
func SaveCFile2(toc *cast.Ir2ToC, file string, outfile string) bool {
	tmp, err := os.MkdirTemp("", "c*")
	utils.MustErr(err)
	file = filepath.Join(tmp, file)
	fd, err := os.Create(file)
	utils.MustErr(err)
	_, err = fd.WriteString(toc.C())
	utils.MustErr(err)
	fd.Close()
	clist := append([]string(nil), file)
	// if toc.InAutoFree {
	// 	clist = append(clist, utils.StdOne["mempool"].CFile...)
	// }
	if _CCBuild(tmp, clist, outfile) { //调用C工具链生成可执行文件
		utils.MustErr(os.RemoveAll(tmp))
		return true
	}
	fmt.Println("C文件在", tmp)
	return false
}
